/*
Copyright (c) 2014-2023 AscEmu Team <http://www.ascemu.org>
This file is released under the MIT license. See README-MIT for more information.
*/

#pragma once

#include <array>
#include <vector>
#include <list>

#include "Objects/Object.hpp"
#include "CommonTypes.hpp"

class Creature;
class Unit;
class SpellInfo;
class ThreatReference;

class SERVER_DECL ThreatManager
{
public:
    static const uint32_t THREAT_UPDATE_INTERVAL = 1000u;
    //class ThreatListIterator;
    static bool canHaveThreatList(Unit const* who);

    ThreatManager(Unit* owner);
    ~ThreatManager();

    void initialize();
    void update(uint32_t tdiff);

    Unit* getOwner() const { return _owner; }

    // can our owner have a threat list?
    bool canHaveThreatList() const { return _ownerCanHaveThreatList; }
    // returns the current victim - this can be nullptr if owner's threat list is empty, or has only offline targets
    Unit* getCurrentVictim();
    Unit* getLastVictim() const;
    Unit* getSecondMostHated();
    // returns an arbitrary non-offline victim from owner's threat list if one exists, nullptr otherwise
    Unit* getAnyTarget() const;

    // are there any entries in owner's threat list?
    bool isThreatListEmpty(bool includeOffline = false) const;
    // is there a threat list entry on owner's threat list with victim == who?
    bool isThreatenedBy(uint64_t const& who, bool includeOffline = false) const;
    // is there a threat list entry on owner's threat list with victim == who?
    bool isThreatenedBy(Unit const* who, bool includeOffline = false) const;
    inline auto const& getThreatenedByMeList() const { return _threatenedByMe; }

    // returns ThreatReference amount if a ref exists, 0.0f otherwise
    float getThreat(Unit const* who, bool includeOffline = false) const;
    size_t getThreatListSize() const { return _sortedThreatList.size(); }
    std::vector<ThreatReference*> getModifiableThreatList();

    void evaluateSuppressed(bool canExpire = false);

    //////////////////////////////////////////////////////////////////////////////////////////
    // Affect my threat list
    void addThreat(Unit* target, float amount, SpellInfo const* spell = nullptr, bool ignoreModifiers = false, bool ignoreRedirects = false, Spell* castingSpell = nullptr);

    void scaleThreat(Unit* target, float factor);

    // Modify target's threat by +percent%
    void modifyThreatByPercent(Unit* target, int32 percent) { if (percent) scaleThreat(target, 0.01f*float(100 + percent)); }

    // Sets the specified unit's threat to be equal to the highest entry on the threat list
    void matchUnitThreatToHighestThreat(Unit* target);

    // Resets the specified unit's threat to zero
    void resetThreat(Unit* target) { scaleThreat(target, 0.0f); }

    // Notify the ThreatManager that we have a new taunt aura (or a taunt aura expired)
    void tauntUpdate();

    // Sets all threat refs in owner's threat list to have zero threat
    void resetAllThreat();

    // Removes specified target from the threat list
    void clearThreat(Unit* target);
    void clearThreat(ThreatReference* ref);
    // Removes all targets from the threat list (will cause evade in UpdateVictim if called)
    void clearAllThreat();

    // Fixate on the passed target; this target will always be selected until the fixate is cleared
    // (if the target is not in the threat list, does nothing)
    void fixateTarget(Unit* target);
    void clearFixate() { fixateTarget(nullptr); }
    Unit* getFixateTarget() const;

    //////////////////////////////////////////////////////////////////////////////////////////
    // Affect others' threat lists 
    // what it says on the tin - call AddThreat on everything that's threatened by us with the specified params
    void forwardThreatForAssistingMe(Unit* assistant, float baseAmount, SpellInfo const* spell = nullptr, bool ignoreModifiers = false);
    // delete all ThreatReferences with victim == owner
    void removeMeFromThreatLists();

    //////////////////////////////////////////////////////////////////////////////////////////
    // Redirect system
    // Register a redirection effect that redirects pct% of threat generated by owner to victim
    void registerRedirectThreat(uint32_t spellId, uint64_t const& victim, uint32_t pct);
    // Unregister a redirection effort for all victims
    void unregisterRedirectThreat(uint32_t spellId);
    // Unregister a redirection effect for a specific victim
    void unregisterRedirectThreat(uint32_t spellId, uint64_t const& victim);

private:
    Unit* const _owner;
    bool _ownerCanHaveThreatList;

    static bool compareReferencesLT(ThreatReference const* a, ThreatReference const* b, float aWeight);
    static float calculateModifiedThreat(Unit* owner, float threat, Unit* victim, SpellInfo const* spell, Spell* castingSpell = nullptr);

    // Attacking me
    void putThreatListRef(uint64_t const& guid, ThreatReference* ref);
    void purgeThreatListRef(uint64_t const& guid);

    bool _needClientUpdate;
    uint32_t _updateTimer;

    std::list<ThreatReference*> _sortedThreatList;
    std::unordered_map<uint64_t, ThreatReference*> _myThreatListEntries;
    std::list<uint64_t> _tauntEffects;

    // picks a new victim
    void updateVictim();

    ThreatReference const* reselectVictim();
    ThreatReference const* _currentVictimRef;
    ThreatReference const* _fixateRef;

    // Attaked from me
    void putThreatenedByMeRef(uint64_t const& guid, ThreatReference* ref);
    void purgeThreatenedByMeRef(uint64_t const& guid);
    std::unordered_map<uint64_t, ThreatReference*> _threatenedByMe; // these refs are entries for myself on other units' threat lists 
    std::array<float, TOTAL_SPELL_SCHOOLS> _singleSchoolModifiers; // most spells are single school - we pre-calculate these and store them
    mutable std::unordered_map<std::underlying_type<SchoolMask>::type, float> _multiSchoolModifiers; // these are calculated on demand

     // redirect system
    void updateRedirectInfo();
    std::vector<std::pair<uint64_t, uint32_t>> _redirectInfo; // current redirection targets and percentages
    std::unordered_map<uint32_t, std::unordered_map<uint64_t, uint32_t>> _redirectRegistry; // spellid -> (victim -> pct); all redirection effects on us

    void sendClearAllThreatToClients() const;
    void sendRemoveToClients(Unit const* victim) const;
    void sendThreatListToClients(bool newHighest) const;

    void heapNotifyChanged();

public:
    ThreatManager(ThreatManager const&) = delete;
    ThreatManager& operator=(ThreatManager const&) = delete;

    friend class ThreatReference;
};

class SERVER_DECL ThreatReference
{
public:
    enum TauntState : uint32_t { TAUNT_STATE_DETAUNT = 0, TAUNT_STATE_NONE = 1, TAUNT_STATE_TAUNT = 2 };
    enum OnlineState { ONLINE_STATE_ONLINE = 2, ONLINE_STATE_SUPPRESSED = 1, ONLINE_STATE_OFFLINE = 0 };

    Creature* getOwner() const { return _owner; }
    Unit* getVictim() const { return _victim; }
    float getThreat() const { return std::max<float>(_baseAmount + (float)_tempModifier, 0.0f); }

    OnlineState getOnlineState() const { return _online; }
    bool isOnline() const { return (_online >= ONLINE_STATE_ONLINE); }
    bool isAvailable() const { return (_online > ONLINE_STATE_OFFLINE); }
    bool isSuppressed() const { return (_online == ONLINE_STATE_SUPPRESSED); }
    bool isOffline() const { return (_online <= ONLINE_STATE_OFFLINE); }
    TauntState getTauntState() const { return isTaunting() ? TAUNT_STATE_TAUNT : _taunted; }
    bool isTaunting() const { return _taunted >= TAUNT_STATE_TAUNT; }
    bool isDetaunted() const { return _taunted == TAUNT_STATE_DETAUNT; }

    void addThreat(float amount);
    void scaleThreat(float factor);
    void modifyThreatByPercent(int32_t percent) { if (percent) scaleThreat(0.01f*float(100 + percent)); }
    void updateOffline();

    void clearThreat(); // dealloc's this

private:
    static bool flagsAllowFighting(Unit const* a, Unit const* b);

    ThreatReference(ThreatManager* mgr, Unit* victim) :
        _owner(reinterpret_cast<Creature*>(mgr->_owner)), _mgr(*mgr), _victim(victim),
        _baseAmount(0.0f), _tempModifier(0), _taunted(TAUNT_STATE_NONE)
    {
        _online = ONLINE_STATE_OFFLINE;
    }

    void unregisterAndFree();

    bool shouldBeOffline() const;
    bool shouldBeSuppressed() const;
    void updateTauntState(TauntState state = TAUNT_STATE_NONE);
    Creature* const _owner;
    ThreatManager& _mgr;
    Unit* const _victim;
    OnlineState _online;
    float _baseAmount;
    int32 _tempModifier;
    TauntState _taunted;

public:
    ThreatReference(ThreatReference const&) = delete;
    ThreatReference& operator=(ThreatReference const&) = delete;

    friend class ThreatManager;
    friend struct CompareThreatLessThan;
};
